import { Texture } from "pixi.js";
import { CompositeTilemap } from "@pixi/tilemap"
import { TileClassifier } from "@/utils/TileClassifier.js";
import { TileAutoDecoder } from "@/utils/TileAutoDecoder.js";
import { MyContainer } from "@/containers/MyContainer.js";
import { ImageManager } from "@/managers/ImageManager.js";
import { config } from "@/managers/ConfigManager.js";
import { mod } from "@/utils/number.js";
export class MapContainer extends MyContainer {
    constructor() {
        super();
        
        this.absWidth = config.width;
        this.absHeight = config.height;
        this.tileWidth = 48;
        this.tileHeight = 48;
        this.tileCols = 0;
        this.tileRows = 0;
        this.tileData = null;
        this.tileset=null;
        this.needsRepaint=false;
        this.lastStartX=null;
        this.lastStartY=null;

        this.sources = [];
        this.taskCount=0;
        this.refreshCall=null;

        this.animationCount = 0;
        this.animationFrame = 0;

        this.horizontalWrap = false;
        this.verticalWrap = false;

        this.tileAnim=[0,0];

        this.sortableChildren=true;
        this.createLayers();

        this.attachHanlder();
    }

    destroy(opts){
        opts=opts||{};
        if(opts.textureSource){
            opts.textureSource=false;
        }

        for(let source of this.sources){
            ImageManager.releaseTileset(source?.key);
        }
        this.deattachHandler();
        super.destroy(opts);
        this.lower=null;
        this.upper=null;
        TileClassifier.flags=[];
    }

    attachHanlder(){
        $gameMap.on('map:tileset',this.onTilesetEvents);
        $gameMap.on('map:display',this.onViewEvents);
    }

    deattachHandler(){
        $gameMap.off('map:tileset',this.onTilesetEvents);
        $gameMap.off('map:display',this.onViewEvents);
    }

    onTilesetEvents=(type)=>{
        if(type=='changed'){
            this.loadTileset();
        }
    }

    onViewEvents=(type)=>{
        if(type=='changed'){
            this.redraw();
        }
    }

    onAnimChange() {
        let af = this.animationFrame % 4;
        if (af == 3) af = 1;
        if (this.lower) {

            let tileAnim = this.tileAnim;

            tileAnim[0] = af * this.tileWidth;
            tileAnim[1] = (this.animationFrame % 3) * this.tileHeight;

            this.lower.tileAnim = this.upper.tileAnim = tileAnim;
        }
    }

    createLayers() {
        this.lower = new CompositeTilemap();
        this.upper = new CompositeTilemap();

        this.lower.zIndex = 0;
        this.upper.zIndex = 4;

        this.addChild(this.lower, this.upper);
    }

    setData(cols, rows, data) {
        this.tileCols = cols;
        this.tileRows = rows;
        this.tileData = data;
    }

    loadTileset(){
        let dataTileset=$gameMap.tileset();
        this.tileset=dataTileset;
        if (dataTileset) {
            let tilesetNames = dataTileset.tilesetNames;
            let len=tilesetNames.length;
            for(let i=0;i<tilesetNames.length;++i){
                let name=tilesetNames[i];
                let source=this.sources[i];
                if(source&&source.key==name)continue;
                else{
                    if(!name){
                        this.sources[i]=Texture.EMPTY.source;
                    }else{
                        ImageManager.releaseTileset(source?.key);
                        ++this.taskCount;
                        ImageManager.loadTileset(name).then(source=>{
                            this.sources[i]=source;
                            --this.taskCount;
                            if(this.taskCount<=0){
                                this.refreshCall?.();
                            }
                        });
                    }
                }
            }
            if(len<this.sources.length){
                for(let i=len;i<this.sources.length;++i){
                    ImageManager.releaseTileset(this.sources[i]?.key);
                }
            }
            this.sources.length=len;
            
            let flags = $gameMap.tilesetFlags();
            
            if (TileClassifier.flags!=flags) {
                TileClassifier.flags = flags;
                this.refresh();
            }
        }
    }

    update() {
        this.animationCount++;
        if(this.animationCount>=30){
            this.animationCount=0;
            ++this.animationFrame;
            this.onAnimChange();
        }
        super.update();
    }

    refresh=()=>{
        if(this.taskCount>0){
            this.refreshCall=this.refresh
            return;
        }
        this.lower.tileset(this.sources);
        this.upper.tileset(this.sources);
        this.redraw();
    }

    tryRedraw(){
        if(window.$gameMap){
            let startX = Math.floor($gameMap.displayX());
            let startY = Math.floor($gameMap.displayY());

            if (this.needsRepaint 
                ||this.lastStartX != startX 
                ||this.lastStartY != startY) {
                
                this.lastStartX = startX;
                this.lastStartY = startY;
                this.paintAllTiles(startX, startY);
                this.needsRepaint = false;
            }
        }
    }

    redraw(){
        this.paintAllTiles(
            Math.floor($gameMap.displayX()),
            Math.floor($gameMap.displayY())
        );
    }

    paintAllTiles(colStart, rowStart) {
        this.lower.clear();
        this.upper.clear();
        let tileCols = Math.min(Math.ceil(this.absWidth / this.tileWidth),this.tileCols);
        let tileRows = Math.min(Math.ceil(this.absHeight / this.tileHeight),this.tileRows);
        for (let y = 0; y < tileRows; y++) {
            for (let x = 0; x < tileCols; x++) {
                this.paintTiles(colStart, rowStart, x, y);
            }
        }
    }

    drawTile(layer, tileId, dx, dy) {
        if (TileClassifier.isVisibleTile(tileId)) {
            if (TileClassifier.isAutotile(tileId)) {
                this.drawAutotile(layer, tileId, dx, dy);
            } else {
                this.drawNormalTile(layer, tileId, dx, dy);
            }
        }
    }

    drawNormalTile(layer, tileId, dx, dy) {
        let setNumber = 0;

        if (TileClassifier.isTileA5(tileId)) {
            setNumber = 4;
        } else {
            setNumber = 5 + (tileId>>8);
        }

        let w = this.tileWidth;
        let h = this.tileHeight;
    
        /**
         * 这里解释也比较逆天
         * 选择块
         * sx=page*8+col
         * sy=row
         * 
         * 取tileId的低8位
         *  p   r r r r  c c c
         *  |      |       |
         * page   row     col
         * 
         * 这里col范围0~7，表示一行的8个图块
         * page 只有0，1；这个将图片的一行分成前8列和后8列，比如有的图片一行16列有的一行8列
         * page和col组合可以表示0~15列的图片
         * 
         * row则可以取0~15
         * 
         */
        
        let sx = (((tileId&0x80) >>4)|(tileId&0x07)) * w;
        let sy = ((tileId&0x78)>>3) * h;

        layer.tile(setNumber,dx,dy,this.makeTileOption(sx,sy,w,h));
        
    }

    drawAutotile(layer, tileId, dx, dy) {
        
        let {
            setNumber=0,
            autotileTable=TileClassifier.FLOOR_AUTOTILE_TABLE,
            baseCol=0,
            baseRow=0,
            animX=0,
            animY=0,
            isTable=false
        }=TileAutoDecoder.decode(tileId);

        let table = autotileTable[TileClassifier.getAutotileShape(tileId)];
        let w1 = this.tileWidth / 2;
        let h1 = this.tileHeight / 2;
        for (let i = 0; i < 4; i++) {
            let qsx = table[i][0];
            let qsy = table[i][1];
            let sx1 = (baseCol * 2 + qsx) * w1;
            let sy1 = (baseRow * 2 + qsy) * h1;
            let dx1 = dx + (i&0x01) * w1;
            let dy1 = dy + ((i&0x02)>>1) * h1;
            if (isTable && (qsy === 1 || qsy === 5)) {
                let qsx2 = qsx;
                let qsy2 = 3;
                if (qsy === 1) {
                    //qsx2 = [0, 3, 2, 1][qsx];
                    qsx2 = (4-qsx)%4;
                }
                let sx2 = (baseCol * 2 + qsx2) * w1;
                let sy2 = (baseRow * 2 + qsy2) * h1;
                layer.tile(setNumber,dx1, dy1,this.makeTileOption(sx2,sy2,w1,h1, animX, animY));
                layer.tile(setNumber,dx1, dy1+h1/2,this.makeTileOption(sx1, sy1,w1, h1/2, animX, animY));
            
            } else {
                layer.tile(setNumber,dx1, dy1,this.makeTileOption(sx1, sy1,w1,h1, animX, animY));
            }
        }
    }

    drawTableEdge(layer, tileId, dx, dy) {
        if (TileClassifier.isTileA2(tileId)) {
            let autotileTable = TileClassifier.FLOOR_AUTOTILE_TABLE;
            let kind = TileClassifier.getAutotileKind(tileId);
            let shape = TileClassifier.getAutotileShape(tileId);
            let tx = kind % 8;
            let ty = Math.floor(kind / 8);
            let setNumber = 1;
            let bx = tx * 2;
            let by = (ty - 2) * 3;
            let table = autotileTable[shape];
            let w1 = this.tileWidth / 2;
            let h1 = this.tileHeight / 2;
            for (let i = 0; i < 2; i++) {
                let qsx = table[2 + i][0];
                let qsy = table[2 + i][1];
                let sx1 = (bx * 2 + qsx) * w1;
                let sy1 = (by * 2 + qsy) * h1 + h1 / 2;
                let dx1 = dx + (i % 2) * w1;
                let dy1 = dy + Math.floor(i / 2) * h1;
                layer.tile(setNumber,dx1,dy1,this.makeTileOption(sx1,sy1,w1,h1/2));
            }
        }
    }

    drawShadow(layer, shadowBits, dx, dy) {
        if (shadowBits & 0x0f) {
            let w1 = this.tileWidth / 2;
            let h1 = this.tileHeight / 2;
            for (let i = 0; i < 4; i++) {
                if (shadowBits & (1 << i)) {
                    let dx1 = dx + (i % 2) * w1;
                    let dy1 = dy + Math.floor(i / 2) * h1;
                    layer.tile(0,dx1,dy1,this.makeTileOption(0,0,w1,h1));
                }
            }
        }
    }

    makeTileOption(u,v,tileWidth,tileHeight,animX,animY){
        return {
            u,
            v,
            tileWidth,
            tileHeight,
            animX,
            animY
        };
    }
    

    readMapData(x, y, z) {
        if (this.tileData) {
            let cols = this.tileCols;
            let rows = this.tileRows;
            if (this.horizontalWrap) {
                x = mod(x, cols);
            }
            if (this.verticalWrap) {
                y = mod(y,rows);
            }
            if (x >= 0 &&
                x < cols &&
                y >= 0 &&
                y < rows) {
                    
                return this.tileData[(z * rows + y) * cols + x] ?? 0;
            }
        }
        return 0;
    }

    paintTiles(colStart, rowStart, x, y) {
        let mx = colStart + x;
        let my = rowStart + y;
        let dx = x * this.tileWidth;
        let dy = y * this.tileHeight;
        let tileId0 = this.readMapData(mx, my, 0);
        let tileId1 = this.readMapData(mx, my, 1);
        let tileId2 = this.readMapData(mx, my, 2);
        let tileId3 = this.readMapData(mx, my, 3);
        let shadowBits = this.readMapData(mx, my, 4);
        let upperTileId1 = this.readMapData(mx, my - 1, 1);


        if (TileClassifier.isHigherTile(tileId0)) {
            this.drawTile(this.upper, tileId0, dx, dy);
        } else {
            this.drawTile(this.lower, tileId0, dx, dy);
        }
        if (TileClassifier.isHigherTile(tileId1)) {
            this.drawTile(this.upper, tileId1, dx, dy);
        } else {
            this.drawTile(this.lower, tileId1, dx, dy);
        }

        this.drawShadow(this.lower, shadowBits, dx, dy);
        if (TileClassifier.isTableTile(upperTileId1) && 
        !TileClassifier.isTableTile(tileId1)) {
            if (!TileClassifier.isShadowingTile(tileId0)) {
                this.drawTableEdge(this.lower, upperTileId1, dx, dy);
            }
        }

        if (TileClassifier.isOverpassPosition(mx, my)) {
            this.drawTile(this.upper, tileId2, dx, dy);
            this.drawTile(this.upper, tileId3, dx, dy);
        } else {
            if (TileClassifier.isHigherTile(tileId2)) {
                this.drawTile(this.upper, tileId2, dx, dy);
            } else {
                this.drawTile(this.lower, tileId2, dx, dy);
            }
            if (TileClassifier.isHigherTile(tileId3)) {
                this.drawTile(this.upper, tileId3, dx, dy);
            } else {
                this.drawTile(this.lower, tileId3, dx, dy);
            }
        }
    }

}